package jbrowser.security;

import java.io.PrintWriter;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.swing.JOptionPane;
import jbrowser.services.SecurityManagerService;
import sun.security.provider.PolicyParser;

public class DefaultSecurityManagerService extends SecurityManager implements SecurityManagerService {

    private static final Object POPUP_LOCK = new Object();
    private static final Logger logger = Logger.getLogger(DefaultSecurityManagerService.class.getName());
    private Map<CodeSource, PermissionCollection> permissionCollections = new ConcurrentHashMap<CodeSource, PermissionCollection>();

    private static class InstanceHolder {

        public static final DefaultSecurityManagerService instace = new DefaultSecurityManagerService();
    }

    public DefaultSecurityManagerService() {
    }

    public static DefaultSecurityManagerService getInstance() {
        return InstanceHolder.instace;
    }

    @Override
    public SecurityManager getSecurityManager() {
        return this;
    }

    @Override
    public String getName() {
        return "DefaultSecurityManagerService";
    }

    @Override
    public Enumeration<PolicyParser.GrantEntry> grantElements() {
        return null;
    }

    @Override
    public boolean grant(PolicyParser.GrantEntry grantEntry) {
    return true;
    }

    @Override
    public <T> T sudo(PrivilegedAction<T> action) throws LoginException {
        LoginContext loginContext;
        try {
            loginContext = AccessController.doPrivileged(new PrivilegedExceptionAction<LoginContext>() {
                @Override
                public LoginContext run() throws LoginException {
                    return new LoginContext("root", new RootCallbackHandler());
                }
            });
        } catch (PrivilegedActionException e) {
            throw (LoginException) e.getException();
        }
        loginContext.login();
        Subject subject = loginContext.getSubject();
        T returnedValue = Subject.doAsPrivileged(subject, action, null);
        loginContext.logout();
        return returnedValue;
    }

    @Override
    public <T> T sudo(PrivilegedExceptionAction<T> action) throws LoginException, PrivilegedActionException {
        LoginContext loginContext;
        try {
            loginContext = AccessController.doPrivileged(new PrivilegedExceptionAction<LoginContext>() {
                @Override
                public LoginContext run() throws LoginException {
                    return new LoginContext("root", new RootCallbackHandler());
                }
            });
        } catch (PrivilegedActionException e) {
            throw (LoginException) e.getException();
        }
        loginContext.login();
        Subject subject = loginContext.getSubject();
        T returnedValue = Subject.doAs(subject, action);
        loginContext.logout();
        return returnedValue;
    }

    public void writePolicy(PrintWriter out) {
        for (CodeSource codeSource : permissionCollections.keySet()) {
            out.println("grant codeBase \"" + codeSource.getLocation() + "\" {");
            Enumeration<Permission> permissions = permissionCollections.get(codeSource).elements();
            while (permissions.hasMoreElements()) {
                Permission permission = permissions.nextElement();
                out.print("    permission " + permission.getClass().getName() + " \"" + permission.getName() + "\"");
                String actions = permission.getActions();
                if (actions != null && !actions.isEmpty()) { // OPTIONAL
                    out.print(", \"" + actions + "\"");
                }
                out.println(";");
            }
            out.println("};");
            out.println();
        }
    }

    @Override
    public void checkPermission(final Permission perm) {
        try {
            super.checkPermission(perm);
        } catch (final AccessControlException exception) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    Class[] classContext = getClassContext();
                    logger.log(Level.FINE, "Allow permission? {0}", perm);
                    for (int i = 0; i < classContext.length; i++) {
                        Class clazz = classContext[i];
                        logger.log(Level.FINE, "Evaluating class: {0}", clazz.getName());
                        ProtectionDomain protectionDomain = clazz.getProtectionDomain();
                        if (protectionDomain == null) {
                            logger.log(Level.FINE, "ProtectionDomain is null... [continue]");
                            continue;
                        }
                        boolean implies = protectionDomain.implies(perm);
                        if (!implies) {
                            logger.log(Level.FINE, "ProtectionDomain does not implies permission... [process]");
                            CodeSource codeSource = protectionDomain.getCodeSource();
                            if (codeSource == null) {
                                logger.log(Level.FINEST, "CodeSource is null... [error]");
                                throw exception;
                            } else {
                                if (permissionCollections.containsKey(codeSource) && permissionCollections.get(codeSource).implies(perm)) {
                                    logger.log(Level.FINE, "Permission already allowed... [return]");
                                    return null;
                                }
                            }
                            synchronized (POPUP_LOCK) {
                                String codeSourceLocation = codeSource == null ? "null" : codeSource.getLocation().toString();
                                logger.log(Level.FINE, "CodeSource Location: {0}", codeSourceLocation);
                                String message = "CodeSource Location: " + codeSourceLocation + "\n"
                                        + "Permission: " + perm + "\n"
                                        + "Name: " + perm.getName() + "\n"
                                        + "Actions: " + perm.getActions();
                                int result = JOptionPane.showConfirmDialog(null, message, "Allow Permission", JOptionPane.YES_NO_CANCEL_OPTION);
                                if (result == JOptionPane.YES_OPTION) {
                                    logger.log(Level.FINE, "Allow permission... [add]");
                                    if (permissionCollections.containsKey(codeSource)) {
                                        permissionCollections.get(codeSource).add(perm);
                                        logger.log(Level.FINE, "Permission allowed [old CodeSource]... [return]");
                                    } else {
                                        PermissionCollection permissionCollection = Policy.getPolicy().getPermissions(codeSource);
                                        if (!permissionCollection.equals(Policy.UNSUPPORTED_EMPTY_COLLECTION)) {
                                            permissionCollection.add(perm);
                                            permissionCollections.put(codeSource, permissionCollection);
                                            logger.log(Level.FINE, "Permission allowed [new CodeSource]... [return]");
                                        } else {
                                            logger.log(Level.FINEST, "Policy returns UNSUPPORTED_EMPTY_COLLECTION for CodeSource... [error]");
                                            throw exception;
                                        }
                                    }
                                } else if (result == JOptionPane.NO_OPTION) {
                                    logger.log(Level.FINEST, "DO NOT allow permission... [error]");
                                    throw exception;
                                } else if (result == JOptionPane.CANCEL_OPTION) {
                                    logger.log(Level.FINEST, "DO NOTHING... [return]");
                                    return null;
                                }
                            }
                        }
                    }
                    logger.log(Level.FINEST, "ProtectionDomain not found in ClassContext... [return]");
                    return null;
                }
            });
        }
    }
}